/**
 * Copyright &copy; 2013-2015 山东易科德软件有限公司 All rights reserved.
 */
package cn.net.ecode.modules.sys.utils;

import cn.net.ecode.common.config.Global;
import cn.net.ecode.common.persistence.BaseEntity;
import cn.net.ecode.common.service.ServiceException;
import cn.net.ecode.common.utils.IdGen;
import cn.net.ecode.common.utils.SpringContextHolder;
import cn.net.ecode.common.utils.StringUtils;
import cn.net.ecode.modules.sys.dao.UserDataScopeDao;
import cn.net.ecode.modules.sys.entity.User;
import cn.net.ecode.modules.sys.entity.UserDataScope;

/**
 * 数据范围工具类
 * @author ThinkGem
 * @version 2015-4-25
 */
public class DataScopeUtils {
	
	private static UserDataScopeDao userDataScopeDao = SpringContextHolder.getBean(UserDataScopeDao.class);

	private static final String USER_CACHE_USER_DATA_SCOPE_ = "userDataScope_";
	
	/**
	 * 数据范围过滤
	 * @param entity 当前过滤的实体类
	 * @param sqlMapKey sqlMap的键值，例如设置“dsf”数据范围过滤，详见 example
	 * @param ctrlTypes 内置控制类型（role,company,office），如果读取管理表，则类型后加“_m”，如（role_m,company_m,office_m）
	 * @param bizTableAliasOrFields 业务表对应过滤表别名或需要过滤的权限字段，使用“,”分隔，长度必须与tableTypes保持一致。
	 * @example
	 * 		1）在Service中调用如下两种方式：<br/>
	 * 			// 生成数据权限过滤条件，指定内置控制类型（role,company,office）<br/>
	 * 			DataScopeUtils.genSql(entity, "dsf", "role,company,office", "a,a,c"));<br/>
	 * 			// 生成数据权限过滤条件，主表字段不一致时使用<br/>
	 * 			DataScopeUtils.genSql(entity, "dsf", UserDataScope.CTRL_TYPE_OFFICE, "a.office_code"));<br/>
	 * 		2）MyBatis Mapper 中调用如下两种方式：<br/>
	 * 			采用 EXISTS方式调用	： 将  ${sqlMap.sdf} 放在Where后<br/>
	 * 			采用JOIN方式调用		： 将  ${sqlMap.sdfFrom} 放在Form后 ，将  ${sqlMap.sdfWhere} 放在Where后<br/>
	 */
	public static void genSql(BaseEntity<?> entity, String sqlMapKey, String ctrlTypes, String bizTableAliasOrFields) {
		genSql(entity, sqlMapKey, ctrlTypes, bizTableAliasOrFields, "");
	}
	
	/**
	 * 数据范围过滤
	 * @param entity 当前过滤的实体类
	 * @param sqlMapKey sqlMap的键值，例如设置“dsf”数据范围过滤，详见 example
	 * @param ctrlTypes 内置控制类型（role,company,office），如果读取管理表，则类型后加“_m”，如（role_m,company_m,office_m）
	 * @param bizTableAliasOrFields 业务表对应过滤表别名或需要过滤的权限字段，使用“,”分隔，长度必须与tableTypes保持一致。
	 * @param bizTableUserField 业务表对应表别名或用户字段。过滤只可以查看本人数据。不设置的话，如果没有范围权限，则查不到任何数据。
	 * @example
	 * 		1）在Service中调用如下两种方式：<br/>
	 * 			// 生成数据权限过滤条件，指定内置控制类型（role,company,office）<br/>
	 * 			DataScopeUtils.genSql(entity, "dsf", "role,company,office", "a,a,c"));<br/>
	 * 			// 生成数据权限过滤条件，指定内置控制类型，当没有关联数据范围权限时只查询本人数据的时候使用<br/>
	 * 			DataScopeUtils.genSql(entity, "dsf", "role,company,office", "a,a,c", "a.create_by"));<br/>
	 * 			// 生成数据权限过滤条件，主表字段不一致时使用<br/>
	 * 			DataScopeUtils.genSql(entity, "dsf", UserDataScope.CTRL_TYPE_OFFICE, "a.office_code"));<br/>
	 * 			// 生成数据权限过滤条件，主表字段不一致时，当没有关联数据范围权限时只查询本人数据的时候使用<br/>
	 * 			DataScopeUtils.genSql(entity, "dsf", UserDataScope.CTRL_TYPE_OFFICE, "a.office_code", "a.create_by"));<br/>
	 * 		2）MyBatis Mapper 中调用如下两种方式：<br/>
	 * 			采用 EXISTS方式调用	： 将  ${sqlMap.sdf} 放在Where后<br/>
	 * 			采用JOIN方式调用		： 将  ${sqlMap.sdfFrom} 放在Form后 ，将  ${sqlMap.sdfWhere} 放在Where后<br/>
	 */
	public static void genSql(BaseEntity<?> entity, String sqlMapKey, String ctrlTypes, String bizTableAliasOrFields, String bizTableUserField) {
		
		User user = entity.getCurrentUser();

		// 如果是超级或系统管理员，则不过滤数据
		if (user.isAdmin()) {
			return;
		}
		
		// 如果使用了集团用户模式，并且是集团管理员，则不过滤数据
		if (Global.isUseCorpModel() && User.MGR_TYPE_CORP_ADMIN.equals(user.getMgrType())){
			return;
		}

		StringBuilder sql = new StringBuilder(); // exists 方式  where sql
		StringBuilder fromSql = new StringBuilder(); // join 方式  form sql
		StringBuilder whereSql = new StringBuilder(); // join 方式  where sql

		String[] ctrlType = StringUtils.split(ctrlTypes, ",");
		String[] bizTableAliasOrField = StringUtils.split(bizTableAliasOrFields, ",");

		// 注意tableTypes和tableAlias使用“,”分隔，长度必须相等。
		if (!(ctrlType != null && bizTableAliasOrField != null && ctrlType.length == bizTableAliasOrField.length)) {
			throw new ServiceException("注意ctrlTypes和bizTableAliasOrFields使用“,”分隔，长度必须相等。" 
					+ "ctrlTypes: " + ctrlTypes + " bizTableAliasOrFields: " + bizTableAliasOrFields);
		}

		// 遍历类型生成权限SQL字符串
		for (int i = 0; i < ctrlType.length; i++) {
			
			// 生成别名
			String dataScopeTableAlias = "a" + IdGen.uuidLong();

			// 如果后缀是“_m”，并且是二级管理员，才可读取管理范围表数据
			String manageDataScope = StringUtils.EMPTY;
			if (StringUtils.endsWith(ctrlType[i], "_m") && User.MGR_TYPE_SEC_ADMIN.equals(user.getMgrType())) {
				manageDataScope = "_m";
			}

			// 角色范围（业务表必须包含role_code字段） dataScopeFilter(entity, "dataScope", "role", "a"));
			if ("role".equals(ctrlType[i]) || "role_m".equals(ctrlType[i])) {
				genRoleDataScopeSql(sql, fromSql, whereSql, user, bizTableAliasOrField[i], dataScopeTableAlias, manageDataScope);
			}

			// 公司范围 （业务表必须包含company_code字段） dataScopeFilter(entity, "dsf", "company", "a"));
			else if ("company".equals(ctrlType[i]) || "company_m".equals(ctrlType[i])) {
				genUserDataScopeSql(sql, fromSql, whereSql, user, UserDataScope.CTRL_TYPE_COMPANY, bizTableAliasOrField[i] + ".company_code", bizTableUserField, dataScopeTableAlias, manageDataScope);
			}

			// 组织范围 （业务表必须包含office_code字段） dataScopeFilter(entity, "dsf", "office", "a"));
			else if ("office".equals(ctrlType[i]) || "office_m".equals(ctrlType[i])) {
				genUserDataScopeSql(sql, fromSql, whereSql, user, UserDataScope.CTRL_TYPE_OFFICE, bizTableAliasOrField[i] + ".office_code", bizTableUserField, dataScopeTableAlias, manageDataScope);
			}

			// 其它范围 （业务表自定义关联字段）  dataScopeFilter(entity, "dsf", "SYS_OFFICE", "a.office_code"));
			else {
				genUserDataScopeSql(sql, fromSql, whereSql, user, ctrlType[i], bizTableAliasOrField[i], bizTableUserField, dataScopeTableAlias, manageDataScope);
			}

		}

		// 编译结果预览
		System.out.println("sql: " + sql.toString());
		System.out.println("fromSql: " + fromSql.toString());
		System.out.println("whereSql: " + whereSql.toString());

		// 设置到自定义SQL对象
		entity.getSqlMap().put(sqlMapKey, sql.toString());
		entity.getSqlMap().put(sqlMapKey + "From", fromSql.toString());
		entity.getSqlMap().put(sqlMapKey + "Where", whereSql.toString());

	}

	/**
	 * 生成用户角色数据范围SQL语句
	 */
	private static void genRoleDataScopeSql(StringBuilder sql, StringBuilder fromSql, StringBuilder whereSql, 
			User user,  String bizTableField, String dataScopeTableAlias, String manageDataScope){
		sql.append(" AND EXISTS (SELECT 1 FROM sys_user" + manageDataScope + "_role WHERE user_code = '"
				+ user.getUserCode() + "' AND role_code = " + bizTableField + ".role_code)");
		fromSql.append(" JOIN sys_user" + manageDataScope + "_role " + dataScopeTableAlias + " ON " + dataScopeTableAlias + ".role_code = " + bizTableField + ".role_code");
		whereSql.append(" AND " + dataScopeTableAlias + ".user_code = '" + user.getUserCode() + "'");
	}
	
	/**
	 * 生成用户数据范围SQL语句
	 */
	private static void genUserDataScopeSql(StringBuilder sql, StringBuilder fromSql, StringBuilder whereSql, 
			User user,  String ctrlType, String bizTableField, String bizTableUserField, String dataScopeTableAlias, String manageDataScope){
		
		// 如果控制类型为公司，并且没有开启公司模块，则跳过此条件
		if (UserDataScope.CTRL_TYPE_COMPANY.equals(ctrlType) && !ModuleUtils.getModule("core_extend").getIsEnable()){
			return;
		}
		
		// 获取用户和控制类型的数据个数
		Integer count = (Integer)UserUtils.getCache(USER_CACHE_USER_DATA_SCOPE_ + ctrlType);
		if (count == null){
			UserDataScope uds = new UserDataScope(user, ctrlType);
			uds.setManageDataScope(manageDataScope); // 二级管理员参数
			count = userDataScopeDao.count(uds);
			UserUtils.putCache(USER_CACHE_USER_DATA_SCOPE_ + ctrlType, count);
		}
		
		// 如果控制数据个数为0，则只能查询自己的数据
		if (count == 0){
			if (StringUtils.isNotBlank(bizTableUserField)){
				sql.append(" AND "+bizTableUserField+" = '"+user.getUserCode()+"'");
				whereSql.append(" AND "+bizTableUserField+" = '"+user.getUserCode()+"'");
			}else{
				sql.append(" AND 1=2");
				whereSql.append(" AND 1=2");
			}
		}else{
			sql.append(" AND EXISTS (SELECT 1 FROM sys_user" + manageDataScope + "_data_scope WHERE ctrl_type = '");
			sql.append(ctrlType + "' AND user_code = '" + user.getUserCode() + "' AND ctrl_data = " + bizTableField + ")");
			fromSql.append(" JOIN sys_user" + manageDataScope + "_data_scope " + dataScopeTableAlias + " ON " + dataScopeTableAlias + ".ctrl_data = " + bizTableField);
			whereSql.append(" AND " + dataScopeTableAlias + ".ctrl_type = '" + ctrlType + "' AND " + dataScopeTableAlias + ".user_code = '" + user.getUserCode() + "'");
		}
	}

}
